# -*- coding: utf-8 -*-
# @Time     : 2024/3/12 14:25
# @Author   : Long-Long Qiu
# @FileName : pack_worker.py
# @Product  : PyCharm
# @description  : 进行pack计算的worker进程
# import packages
import time
import os
import json
from py3dbp import Packer, Bin, Item, Painter
import pickle
import math
import argparse


def packing(box, items, radius_threshold1, radius_threshold2, pre_packing_cubes=True):

    # 预处理：过滤数量小于1的项
    items = list(filter(lambda x: x[4] > 0, items))
    # 1. 获取box的高度
    if type(box[0]) == list or type(box[0]) == tuple:
        # box_length = box[0][0]
        # box_width = box[0][1]
        # box_height = box[0][2]
        box_length = max([be[0] for be in box])
        box_width = max([be[1] for be in box])
        box_height = max([be[2] for be in box])
    elif type(box[0]) == Bin:
        # box_length = float(box[0].width)
        # box_width = float(box[0].height)
        # box_height = float(box[0].depth)
        box_length = float(max([be.width for be in box]))
        box_width = float(max([be.height for be in box]))
        box_height = float(max([be.depth for be in box]))
    else:
        box_length, box_width, box_height = 0, 0, 0

    cubes = list(filter(lambda x: x[5] != 0, items))    # 所有的非盘具
    items = list(filter(lambda x: x[5] == 0, items))    # 所有的盘具

    if sum([ie[4] for ie in items]) > 0:
        heaps = pack_cylinders_2(items)
        heaps = {f"heap-{index + 1}": heap for index, heap in enumerate(heaps)}
    else:
        heaps = {}

    # 2. 将货物进行打包预处理
    # 2.1 预打包盘货：将盘货items转成heaps
    # if sum([ie[4] for ie in items]) > 0:
    #     heaps = pack_cylinders(items, radius_threshold1, radius_threshold2, box_height)
    #     heaps = {f"heap-{index + 1}": heap for index, heap in enumerate(heaps)}
    # else:
    #     heaps = {}

    if pre_packing_cubes:   # 对cubes进行预打包处理
    # 2.2 预打包cubes：将cubes转成heaps
        pre_pack_cubes = pack_cubes2
        cube_heaps, other_cubes, finished = pre_pack_cubes(cubes, box_length, box_width, box_height)

        while not finished:  # 预打包未结束
            # 继续进行预打包
            new_cube_heaps, other_cubes, finished = pre_pack_cubes(other_cubes, box_length, box_width, box_height, start_index=len(cube_heaps))
            cube_heaps = {**cube_heaps, **new_cube_heaps}
        # 3. 装箱
        result = pack_heap(heaps, box, cubes=other_cubes, cube_heaps=cube_heaps)
        return {**heaps, **cube_heaps}, result
    else:
        result = pack_heap(heaps, box, cubes=cubes)
        return heaps, result


def pack_cylinders_2(all_items):
    results = []
    for items in all_items:
        for i in range(items[4]):
            results.append([[[items[0], items[1], items[2], items[3], 0, 0, 0, 'orange']]])
    return results


def pack_cubes2(cubes, box_width, box_height, box_depth, start_index=0):
    # 1. 将木箱、托盘、纸箱分开
    woodens = [cube for cube in cubes if cube[5] == 1]
    trays = [cube for cube in cubes if cube[5] == 2]
    cartoons = [cube for cube in cubes if cube[5] == 3]

    # box_width /= 5
    # box_height /= 5

    results = {}
    flag = True

    # 2. 对不同种类的货物分别做处理
    # 2.1 预打包木箱
    for wooden in woodens:
        pass

    # 2.2 预打包托盘
    other_trays = []
    # 优先在y轴方向上平铺，其次在z轴方向上堆叠，最后在x轴方向上平铺
    for index, tray in enumerate(trays):
        # 获取货物的尺寸和重量
        width, height, depth, weight = tray[0:4]
        # 确定沿着y轴和z轴能放几个货物
        y_layers_num = math.floor(box_height // height)
        z_layers_num = 1
        if width * height / 1000 / 1000 > 0.6 and 100 < weight < 600:
            z_layers_num = math.floor(box_depth // depth)
        # x_layers_num = math.floor(box_width // width) // 20
        x_layers_num = 1
        layers = [[] for _ in range(z_layers_num)]
        x, y, z = 0., 0., 0.
        num = tray[4]
        w, h, d = 0, 0, 0
        for i in range(num):
            layers[d].append([round(width, 2), round(height, 2), round(depth, 2),
                              round(weight, 2), round(x, 2), round(y, 2), round(z, 2)])

            if h == y_layers_num - 1:  # y轴铺满，往z轴堆叠或x轴平铺
                if d == z_layers_num - 1:  # y轴铺满且z轴也堆满了，则往x轴方向平铺
                    w += 1
                    h = 0
                    d = 0
                    if w >= x_layers_num and num > i:  # 车厢放不下
                        other_trays.append([width, height, depth, weight, num - i - 1, 2])
                        break
                else:  # y轴铺满而z轴未堆满，则往z轴方向堆叠
                    h = 0
                    d += 1

                x, y, z = w * width, h * height, d * depth

            else:  # y轴未铺满，沿着y轴方向继续平铺
                h += 1
                y = h * height
        results[f"tray-{index + start_index}"] = list(filter(lambda m: m != [], layers))

    # 2.3 预打包纸箱
    other_cartoons = []
    # 优先在y轴方向上平铺，其次在z轴方向上堆叠，最后在x轴方向上平铺
    for index, cartoon in enumerate(cartoons):
        # 获取货物的尺寸和重量
        width, height, depth, weight = cartoon[0:4]
        # 确定沿着y轴和z轴能放几个货物
        y_layers_num = math.floor(box_height // height)
        z_layers_num = math.floor(box_depth // depth)
        # x_layers_num = math.floor(box_width // width) // 20
        x_layers_num = 1
        layers = [[] for _ in range(z_layers_num)]
        x, y, z = 0., 0., 0.
        num = cartoon[4]
        w, h, d = 0, 0, 0
        for i in range(num):
            layers[d].append([round(width, 2), round(height, 2), round(depth, 2),
                              round(weight, 2), round(x, 2), round(y, 2), round(z, 2)])

            if h == y_layers_num - 1:   # y轴铺满，往z轴堆叠或x轴平铺
                if d == z_layers_num - 1:   # y轴铺满且z轴也堆满了，则往x轴方向平铺
                    w += 1
                    h = 0
                    d = 0
                    if w >= x_layers_num and num > i:   # 车厢放不下
                        other_cartoons.append([width, height, depth, weight, num - i - 1, 3])
                        break
                else:   # y轴铺满而z轴未堆满，则往z轴方向堆叠
                    h = 0
                    d += 1

                x, y, z = w * width, h * height, d * depth

            else:   # y轴未铺满，沿着y轴方向继续平铺
                h += 1
                y = h * height
        results[f"cartoon-{index + start_index}"] = list(filter(lambda m: m != [], layers))

    # if len(other_trays) > preMinNum or len(other_cartoons) > preMinNum:
    if other_trays or other_cartoons:
        flag = False
    return results, other_trays + woodens + other_cartoons, flag


def pack_heap(heaps, boxes, cubes=[], cube_heaps={}, use_box_height=True):
    """
    将heap装载到box中
    :param heaps:
    :param boxes:
    :param cubes:
    :param use_box_height: 是否使用bin的高度作为heap的高度(可以用于防止在heap上方继续堆叠)
    :return:
    """

    # 1. 初始化packer对象，并将box添加到packer中
    packer = Packer()
    for idx, b in enumerate(boxes):
        if type(b) == list or type(b) == tuple:
            bin = Bin(f"car-{idx}", (b[0], b[1], b[2]), b[3], corner=0, put_type=0, price=b[4])
            packer.addBin(bin)
        elif type(b) == Bin:
            packer.addBin(b)

    # 2. 添加cylinder的heap对象
    colors = ['orange', 'olive', 'green', 'red', 'blue', 'purple', 'yellow']
    for name, heap in heaps.items():
        index = int(name.split('-')[1])
        width = heap[0][-1][4] + heap[0][-1][0]
        height = heap[0][0][1]
        depth = [item for layer in heap for item in layer][-1][6] + heap[0][0][2]
        if use_box_height:
            # depth = float(packer.bins[0].depth)
            depth = max([float(ib.depth) for ib in packer.bins])
        weight = sum([item[3] for layer in heap for item in layer])

        # print('cylinder-heap', width, height, depth)
        packer.addItem(
            Item(partno=f'Box-{index + 1}', name=name, typeof='heap',
                 WHD=(width, height, depth), weight=weight, level=0, loadbear=100,
                 updown=False, color=colors[index % len(colors)]))

    # 3. 添加cube的heap对象
    for name, heap in cube_heaps.items():
        if not heap:
            continue
        level_map = {'wooden': 1, 'tray': 2, 'cartoon': 3}
        # width = heap[0][-1][0] + heap[0][-1][4]
        width = max([ie[0] + ie[4] for layer in heap for ie in layer])
        # height = heap[0][-1][1] + heap[0][-1][5]
        height = max([ie[1] + ie[5] for layer in heap for ie in layer])
        depth = heap[-1][0][2] + heap[-1][0][6]
        # weight = heap[0][0][3]
        weight = sum([ie[3] for layer in heap for ie in layer])
        kind, index = name.split('-')[0], int(name.split('-')[1])
        packer.addItem(Item(partno=f"CubeBox-{index + 1}", name=name, typeof='cube',
                            WHD=(width, height, depth), weight=weight, level=level_map[kind],
                            loadbear=100, updown=False, color=colors[index % len(colors)]))

    # 4. 添加额外的cubes(1为木箱，2为托盘，3为纸箱，使用item.level进行区分)
    cube_colors = {
        1: 'purple',    # 木箱为紫色
        2: 'cyan',  # 托盘蓝绿色
        3: 'gray'   # 纸箱为灰色
    }
    cube_num = 1
    for index, cube in enumerate(cubes):
        n = cube[4]
        for _ in range(n):
            packer.addItem(Item(partno=f'Box-{cube_num + len(heaps)}', name=f'cube{cube_num + len(heaps)}', typeof='cube', WHD=(cube[0], cube[1], cube[2]),
                 weight=cube[3], level=cube[5], loadbear=cube[3],
                 updown=False, color=cube_colors[cube[5]]))
            cube_num += 1

    # 5. 装箱
    packer.pack(
        bigger_first=False,
        distribute_items=False,
        fix_point=True,
        check_stable=True,
        support_surface_ratio=1.,
        number_of_decimals=2
    )
    # put order
    packer.putOrder()
    return packer


def packing_items(items, all_cars, result_path, log_path, config_parameters={}, user_ip='127.0.0.1', pre_packing_cubes=True, pre_min_num=20, top_num=5):
    start = time.time()
    # 1. 准备结果文件
    absolute_file_path = f'{result_path}/result.json'
    if not os.path.exists(absolute_file_path):
        with open(absolute_file_path, 'w') as fp:
            json.dump({
                'finished': False,
                'result': []
            }, fp)

    # 2. 获取货物、车辆等信息
    if sum([ie[4] for ie in items]) < pre_min_num:
        pre_packing_cubes = False
    # radius1 = int(config_parameters['dish']['second_dish_radius_1']) / 100
    # radius2 = int(config_parameters['dish']['second_dish_radius_2']) / 100
    radius1 = 500
    radius2 = 700
    cars = [(car['size'][0], car['size'][1], car['size'][2], car['loadbear'], car['price']) for car in all_cars]
    cars = sorted(cars, key=lambda x: x[4], reverse=True)

    ie_v, ie_w = 0, 0
    for ie in items:
        ie_w += ie[3] * ie[4]
        ie_v += ie[0] * ie[1] * ie[2] * ie[4]

    results = []
    # 3. 计算单车方案
    suc_num = 0
    for index, b in enumerate(cars):
        box = Bin(f"car-{index}", (b[0], b[1], b[2]), b[3], 0, put_type=0, price=b[4])
        box_v = float(box.getVolume())
        box_w = float(box.max_weight)
        if box_v < ie_v or box_w < ie_w:
            continue
        # 3.1 尝试装箱
        heaps, pack_result = packing([box], items, radius1, radius2, pre_packing_cubes=pre_packing_cubes)
        # 3.2 判断装箱是否成功
        if len(pack_result.bins[0].items) == pack_result.total_items:
            suc_num += 1
            # 3.3 计算空间利用率、载重利用率等信息
            space_ratio = ie_v / box_v
            weight_ratio = ie_w / box_w
            car_type = f"car{index}-{[a / 1000 for a in cars[index][0:3]]}"
            # 3.4 结果汇总
            results.append({
                'status': True,
                'bin_type': [car_type],
                'bin_num': [1],
                'space_ratio': space_ratio,
                'box_weight': box_w,
                'goods_weight': ie_w,
                'weight_ratio': weight_ratio,
                'price': b[4],
                'goods_volume': ie_v / 100 / 100 / 100,
                'bin_volume': box_v / 100 / 100 / 100,
                'heaps': str(pickle.dumps(heaps), encoding='latin1'),
                'packer': str(pickle.dumps(pack_result), encoding='latin1'),
                'car-num': 1
            })
            # 3.5 结果保存
            with open(absolute_file_path, 'w') as fp:
                json.dump({
                    'ts': int(time.time()),
                    'finished': False,
                    'result': results
                }, fp, ensure_ascii=False)



    # 4. 计算多车方案
    car_types = []      # 记录方案是否已经计算
    multi_cars = [[[car]] for car in cars]
    finished = False
    while not finished:
        if len(multi_cars[0][0]) + 1 >= 11:
            break

        # 4.1 生成方案
        print('*' * 30)
        print(f"开始生成{len(multi_cars[0][0]) + 1}车方案")
        print('*' * 30)
        for cars_index in range(len(multi_cars)):
            current_cars = multi_cars[cars_index]
            cars_temp = []
            for current_car in current_cars:
                for car_index, car in enumerate(cars):
                    if car_index > cars_index:
                        continue
                    cars_temp.append(current_car + [car])
            multi_cars[cars_index] = cars_temp

        # 4.2 方案排序
        multi_boxes = [car for c_cars in multi_cars for car in c_cars]
        multi_boxes = sorted(multi_boxes, key=lambda x: sum([c[4] for c in x]), reverse=True)

        # 4.3 计算方案
        print('*' * 30)
        print(f"开始计算{len(multi_cars[0][0])}车方案")
        print('*' * 30)
        current_suc, current_total = 0, 0
        for boxes_index, boxes in enumerate(multi_boxes):
            bin_v = sum([be[0] * be[1] * be[2] for be in boxes])
            bin_w = sum([be[3] for be in boxes])
            if bin_v < ie_v or bin_w < ie_w:
                continue
            current_total += 1
            # 4.4 尝试装箱
            heaps, pack_result = packing(boxes, items, radius1, radius2, pre_packing_cubes=pre_packing_cubes)
            bin_type, bin_num = [], []
            car_type = []
            for bi in pack_result.bins:
                bin_type.append(
                    f"car-{str([float(bi.width) / 1000, float(bi.height) / 1000, float(bi.depth) / 1000])}")
                car_type.append(str(bi.width))
                bin_num.append(1)
            car_type = sorted(car_type, key=lambda x: x)
            car_type = ','.join(car_type)
            if car_type not in car_types:
                # 4.5 判断装箱是否成功
                status = sum([len(ie.items) for ie in pack_result.bins]) == pack_result.total_items
                if status:
                    current_suc += 1
                    # 4.6 计算空间利用率、载重利用率等信息
                    space_ratio = ie_v / bin_v
                    weight_ratio = ie_w / bin_w
                    suc_num += 1
                    print('成功！！！', car_type)
                    # 4.7 追加结果
                    car_types.append(car_type)
                    results.append({
                        'status': True,
                        'bin_type': bin_type,
                        'bin_num': bin_num,
                        'space_ratio': space_ratio,
                        'box_weight': bin_w,
                        'goods_weight': ie_w,
                        'weight_ratio': weight_ratio,
                        'price': sum([bi.price for bi in pack_result.bins]),
                        'goods_volume': ie_v / 100 / 100 / 100,
                        'bin_volume': bin_v / 100 / 100 / 100,
                        'color': {},
                        'heaps': str(pickle.dumps(heaps), encoding='latin1'),
                        'packer': str(pickle.dumps(pack_result), encoding='latin1'),
                        'car-num': len(bin_type)
                    })
                    # 4.8 结果保存
                    with open(absolute_file_path, 'w') as fp:
                        json.dump({
                            'ts': int(time.time()),
                            'finished': False,
                            'result': results
                        }, fp, ensure_ascii=False)
                    if suc_num >= top_num:
                        finished = True

                else:
                    if 0.1 and current_total > 20:
                        break
            else:
                pass
                # print('重复')

    if finished:
        print('装箱进程结束！！！')
    # 5. 更改结果状态
    with open(absolute_file_path, 'r') as fp:
        data = json.load(fp)
        data['finished'] = True
    with open(absolute_file_path, 'w') as fp:
        json.dump(data, fp, ensure_ascii=False)

    # 6. 日志
    end = time.time()
    print(f"装箱耗时{round(end - start, 3)}秒")
    log_content = {
        'url': '/pack',
        'user_ip': user_ip,
        'start_time': int(start),
        'time': round(end - start, 3),
        'goods': items,
        'cars': all_cars,
        'config': config_parameters,
        'result': results
    }
    with open(log_path, 'a') as fp:
        fp.write(json.dumps(log_content, ensure_ascii=False) + '\n')


parser = argparse.ArgumentParser(description='启动pack_worker进程')
if __name__ == '__main__':
    parser.add_argument('--items', type=str, default='[]', help='货物')
    parser.add_argument('--cars', type=str, default='{}', help='车辆')
    parser.add_argument('--resultPath', type=str, default='./results/1', help='session结果保存路径')
    parser.add_argument('--logPath', type=str, default='./pack.log', help='日志保存路径')
    parser.add_argument('--configParameters', type=str, default='{}', help='算法配置项')   # 暂时不用
    parser.add_argument('--userIp', type=str, default='127.0.0.1', help='请求者ip地址')

    args = parser.parse_args()
    items = json.load(open(args.items, 'r'))
    # items = json.loads(args.items)
    cars = json.load(open(args.cars, 'r'))
    # cars = json.loads(args.cars)
    configParameters = json.loads(args.configParameters)

    packing_items(items, cars, args.resultPath, args.logPath, configParameters, user_ip=args.userIp)

